#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#define TPH_WIDTH_BYTE 108
uint8_t *left;
uint8_t *right;
uint8_t *left1;
uint8_t *right1;

uint8_t *left_base;
uint8_t *right_base;

uint64_t gettime(void)
{
    struct timeval start;
    gettimeofday(&start, NULL);
    uint64_t time = start.tv_sec*1000000ULL + start.tv_usec;
    return time;
}

__attribute__((noinline)) void get_left_right(int size)
{
    left_base = malloc(size);
    right_base = malloc(size);

    left = malloc(size);
    right = malloc(size);

    left1 = malloc(size + 4);
    right1 = malloc(size + 4);
    left1 += 1;
    right1 += 1;

    srand(time(NULL));
    for (int i = 0; i < size; i++) {
        left[i] = rand()%256;
    }

    /* for (int i = 0; i < size; i++) { */
    /*   right[i] = rand()%256; */
    /*   if (i< 20) */
    /*     printf("%d: left %x, right %x\n", i, left[i], right[i]); */
    /* } */
    memcpy(right, left, size);
    memcpy(left1, left, size);
    memcpy(right1, left, size);

    memcpy(left_base, left, size);
    memcpy(right_base, left, size);

}

void compare(int size)
{
    for (int i = 0; i < size; i++){
        if (left[i]!=left1[i] || right[i] != right1[i])
            /* if (left[i]!=left1[i]) */{
            printf("**error %d: left %x, left1 %x, right %x, right1 %x\n",
                   i, left[i], left1[i], right[i], right1[i]);
            break;
        }
        if (i < 20)
            printf("compare %d: left %x, right %x\n", i, left[i], right[i]);
    }
}
__attribute__((noinline)) int orgin(int loops, int bytes, int ops)
{
    int j, k, i;
    uint8_t *R,*L;
    for (int loop = 0; loop < loops; loop++) {
        memcpy(right, right_base, bytes);
        memcpy(left, left_base, bytes);

        R= right;
        L = left;

        if (ops) {
            j=0,k=0;

            for (i = 0; i < bytes - 1; i++,L++) {
                j = (*(L+1))&0x80;
                *L = (*L<<1)|(j>>7);
                /* if (i < 6) */
                /*	printf("L %x\n", *L); */
            }
            *L = *L<<1;

            j=0,k=0;
            for (i = 0; i < bytes; i++,R++) {
                j=*R & 1;
                *R=(*R>>1)|(k<<7);
                /* if (i < 2) */
                /*   printf("R %x\n", *R); */
                k=j;
            }
        } else {
            j=0,k=0;
            for (i = 0; i < bytes; i++,L++) {
                j=(*L)&0x80;
                *L=(*L<<1)|(k>>7);
                k=j;
            }
            j=0,k=0;
            for (i = 0; i < bytes - 1; i++,R++) {
                j=*(R+1)&1;
                *R=(*R>>1)|(j<<7);
            }
            *R = *R>>1;
        }
    }
}

static inline void shift_bytes_bigendian(uint8_t *L,uint8_t* R,int bytes)
{
    uint32_t addr_residue = (uint32_t)L & 0x3;
    uint32_t *L32;
    uint32_t *R32;
    uint32_t shift_carry = 0;
    uint8_t *endL = L + bytes;

    if(addr_residue) {
        // 地址非对齐处理
        uint8_t next = 0;
        uint8_t curr;
        uint8_t c_curr = 0;
        int i;
        for(i = 0;i < 4 - addr_residue;i++) {
            curr = L[0];
            next = L[1];
            L[0] = (curr << 1) | (next >> 7);
            R[0] = (curr >> 1) | c_curr;
            c_curr = curr << 7;
            L++;
            R++;
        }
        shift_carry = c_curr << 24;
    }
    L32 = (uint32_t*)L;
    R32 = (uint32_t*)R;
     /* unroll 12 byte */
    while(L32 < endL - 4 - 12) {
        uint32_t L1 = __builtin_bswap32(L32[0]);
        uint32_t L2 = __builtin_bswap32(L32[1]);
        uint32_t L3 = __builtin_bswap32(L32[2]);
        uint32_t L4 = __builtin_bswap32(L32[3]);
        uint32_t LL1,LL2,LL3,RR1,RR2,RR3;

        LL1 = (L1 << 1) | (L2 >> 31);
        RR1 = (L1 >> 1) | shift_carry;

        LL2 = (L2 << 1) | (L3 >> 31);
        RR2 = (L2 >> 1) | (L1 << 31);

        LL3 = (L3 << 1) | (L4 >> 31);
        RR3 = (L3 >> 1) | (L2 << 31);

        shift_carry = L3 << 31;

        L32[0] = __builtin_bswap32(LL1);
        L32[1] = __builtin_bswap32(LL2);
        L32[2] = __builtin_bswap32(LL3);

        R32[0] = __builtin_bswap32(RR1);
        R32[1] = __builtin_bswap32(RR2);
        R32[2] = __builtin_bswap32(RR3);
        L32 += 3;
        R32 += 3;
    }
    while(L32 < endL - 4 - 4) {
        uint32_t L1 = __builtin_bswap32(L32[0]);
        uint32_t L2 = __builtin_bswap32(L32[1]);
        uint32_t LL1,RR1;
        LL1 = (L1 << 1) | (L2 >> 31);
        RR1 = (L1 >> 1) | shift_carry;
        shift_carry = L1 << 31;
        L32[0] = __builtin_bswap32(LL1);
        R32[0] = __builtin_bswap32(RR1);
        L32 += 1;
        R32 += 1;
    }

    if(L32 == endL - 4) {
        uint32_t L1 = __builtin_bswap32(L32[0]);
        uint32_t LL1,RR1;
        LL1 = (L1 << 1);
        RR1 = (L1 >> 1) | shift_carry;
        L32[0] = __builtin_bswap32(LL1);
        R32[0] = __builtin_bswap32(RR1);
    } else {
        uint8_t LL1,RR1;
        uint8_t L1,L2;
        uint8_t shift_carry_8 = shift_carry >> 24;
        L = (uint8_t*)L32;
        R = (uint8_t*)R32;
        while(L < endL - 1) {
            L1 = L[0];
            L2 = L[1];
            LL1 = (L1 << 1) | (L2 >> 7);
            RR1 = (L1 >> 1) | shift_carry_8;
            shift_carry_8 = L1 << 7;
            L[0] = LL1;
            R[0] = RR1;
            L++;
            R++;
        }
        L1 = L[0];
        L[0] = L1 << 1;
        R[0] =(L1 >> 1) | shift_carry_8;
    }
}

static inline void shift_bytes_littleendian(uint8_t *L,uint8_t* R,int bytes)
{
    uint32_t addr_residue = (uint32_t)L & 0x3;
    uint32_t *L32;
    uint32_t *R32;
    uint32_t shift_carry = 0;
    uint8_t *endL = L + bytes;

    if(addr_residue) {
        // 地址非对齐处理
        uint8_t next = 0;
        uint8_t curr;
        uint8_t c_curr = 0;
        int i;
        for(i = 0;i < 4 - addr_residue;i++) {
            curr = L[0];
            next = L[1];
            L[0] = (curr << 1) | c_curr;
            R[0] = (curr >> 1) | (next << 7);
            c_curr = curr >> 7;
            L++;
            R++;
        }
        shift_carry = c_curr;
    }
    L32 = (uint32_t*)L;
    R32 = (uint32_t*)R;
     /* unroll 12 byte */
    while(L32 < endL - 4 - 12) {
        uint32_t L1 = L32[0];
        uint32_t L2 = L32[1];
        uint32_t L3 = L32[2];
        uint32_t L4 = L32[3];
        uint32_t LL1,LL2,LL3,RR1,RR2,RR3;

        LL1 = (L1 << 1) | shift_carry;
        RR1 = (L1 >> 1) | (L2 << 31);

        LL2 = (L2 << 1) | (L1 >> 31);
        RR2 = (L2 >> 1) | (L3 << 31);

        LL3 = (L3 << 1) | (L2 >> 31);
        RR3 = (L3 >> 1) | (L4 << 31);

        shift_carry = L3 >> 31;

        L32[0] = LL1;
        L32[1] = LL2;
        L32[2] = LL3;

        R32[0] = RR1;
        R32[1] = RR2;
        R32[2] = RR3;
        L32 += 3;
        R32 += 3;
    }

    while(L32 < endL - 4 - 4) {
        uint32_t L1 = L32[0];
        uint32_t L2 = L32[1];
        uint32_t LL1,RR1;
        LL1 = (L1 << 1) | shift_carry;
        RR1 = (L1 >> 1) | (L2 << 31);
        shift_carry = L1 >> 31;
        L32[0] = LL1;
        R32[0] = RR1;
        L32 += 1;
        R32 += 1;
    }

    if(L32 == endL - 4) {
        uint32_t L1 = L32[0];
        uint32_t LL1,RR1;
        LL1 = (L1 << 1) | shift_carry;
        RR1 = (L1 >> 1);
        L32[0] = LL1;
        R32[0] = RR1;
    } else {
        uint8_t LL1,RR1;
        uint8_t L1,L2;
        uint8_t shift_carry_8 = shift_carry;
        L = (uint8_t*)L32;
        R = (uint8_t*)R32;
        while(L < endL - 1) {
            L1 = L[0];
            L2 = L[1];
            LL1 = (L1 << 1) | shift_carry_8;
            RR1 = (L1 >> 1) | (L2 << 7);
            shift_carry_8 = L1 >> 7;
            L[0] = LL1;
            R[0] = RR1;
            L++;
            R++;
        }
        L1 = L[0];
        L[0] = L1 << 1 | shift_carry_8;
        R[0] =(L1 >> 1);
    }
}

__attribute__((noinline)) int optimize(int loops, int bytes, int ops) {
    // 预先计算循环边界
    for (int loop = 0; loop < loops; loop++) {
        memcpy(right1, right_base, bytes);
        memcpy(left1, left_base, bytes);
        if (ops) {
            shift_bytes_bigendian(left1,right1,bytes);
        } else {
            shift_bytes_littleendian(left1,right1,bytes);
        }
    }
    return 0;
}

int main(int argc , char**argv)
{
    int loops = 200000;
    int bytes = TPH_WIDTH_BYTE;
    int ops = 0;

    if (argc >=2) {
        loops = atoi(argv[1]);
    }
    if (argc >=3 ) {
        bytes = atoi(argv[2]);
    }
    if (argc >=4 ) {
        ops = atoi(argv[3]);
    }

    printf("loops %d, bytes %d\n", loops, bytes);

    get_left_right(bytes);
    uint64_t t1 = gettime();
    optimize(loops, bytes,ops);

    uint64_t t2 = gettime();
    orgin(loops, bytes,ops);
    uint64_t t3 = gettime();
    printf("time 1 %lld , time2 %lld\n", t2-t1, t3-t2);

    compare(bytes);
}
